<?php


namespace App\Services;


use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use WechatPay\GuzzleMiddleware\Util\PemUtil;
use WechatPay\GuzzleMiddleware\WechatPayMiddleware;

class WechatPayment
{
    //公众号
    private static $app_id = '';

    //服务ID
    private static $service_id = '';

    //商户号
    private $mch_id  = '';

    //api3密钥
    private $api_key = '';

    //证书序列号
    private $key = '';

    //微信平台证书
    private $wechat_cert = 'cert/wechat/wechatpay_cert.pem';

    //微信商户公钥
    private $cert = 'cert/wechat/cert.pem';

    //微信商户私钥
    private $secret = 'cert/wechat/key.pem';

    const AUTH_TAG_LENGTH_BYTE = 16;

    protected $client;

    public function __construct()
    {
        $wechatpayCertificate = PemUtil::loadCertificate(storage_path($this->wechat_cert));
        $merchantPrivateKey = PemUtil::loadPrivateKey(storage_path($this->secret));

        $wechatpayMiddleware = WechatPayMiddleware::builder()
                                ->withMerchant($this->mch_id,$this->key,$merchantPrivateKey)
                                ->withWechatPay([$wechatpayCertificate])
                                ->build();

        $stack = HandlerStack::create();
        $stack->push($wechatpayMiddleware,'wechatpay');

        $this->client = new Client([
            'handler'   => $stack,
            'base_uri'  => 'https://api.mch.weixin.qq.com/v3/',
            //'debug'     => true,
        ]);
    }

    public static function entity()
    {
        return new WechatPayment();
    }

    public static function sendPost(string $path, array $data, bool $json = true)
    {
        if ($json){
            $query = [
                'json' => $data,
                'headers' => [ 'Accept' => 'application/json' ]
            ];
        }else{

            $query = [
                'form_params'   => $data,
            ];
        }

        $client = self::entity()->client->request('POST',$path,$query);

        return $client->getBody();
    }

    public static function sendGet(string $path, array $data)
    {
        $client = self::entity()->client->get($path.'?'.http_build_query($data),[
            'headers' => [ 'Accept' => 'application/json' ]
        ]);

        return json_decode($client->getBody(),true);
    }

    /**
     * 用户授权
     * @param string $authorization_code
     * @return mixed
     */
    public static function permissions(string $authorization_code)
    {
        if (! RedisTable::objectTable('app:wechat:auth:'.$authorization_code)){

            $result = self::sendPost('payscore/permissions',[
                'service_id'        => self::$service_id,
                'appid'             => self::$app_id,
                'authorization_code'=> $authorization_code,
                'notify_url'        => config('app.url') . '/api/store/notify/wechat'
            ]);
            $data = json_decode($result,true);
            RedisTable::objectTableSave('app:wechat:auth:'.$authorization_code,$data,3600);
        }

        return RedisTable::objectTable('app:wechat:auth:'.$authorization_code);

    }

    /**
     * 协议号查询用户授权
     * @param string $authorization_code
     * @return mixed
     */
    public static function queryAuthorizationCode(string $authorization_code)
    {
        return self::sendGet('payscore/permissions/authorization-code/'.$authorization_code,[
            'service_id'    => self::$service_id,
        ]);
    }

    /**
     * 解除用户协议号
     * @param string $authorization_code
     * @param array $data
     * @return mixed
     */
    public static function terminateAuthorizationCode(string $authorization_code,array $data)
    {
        return self::sendPost('payscore/permissions/authorization-code/'.$authorization_code.'/terminate',collect([
            'service_id'    => self::$service_id,
        ])->merge($data)->toArray());
    }

    /**
     * openID查询授权
     * @param string $openid
     * @return mixed
     */
    public static function queryOpenid(string $openid)
    {
        return self::sendGet('payscore/permissions/openid/'.$openid,[
            'service_id'        => self::$service_id,
            'appid'             => self::$app_id,
        ]);
    }

    /**
     * openID解除协议号
     * @param string $openid
     * @param array $data
     * @return mixed
     */
    public static function terminateOpenid(string $openid,array $data)
    {
        return self::sendPost('payscore/permissions/openid/'.$openid.'/terminate',collect([
            'service_id'        => self::$service_id,
            'appid'             => self::$app_id,
        ])->merge($data)->toArray());
    }

    /**
     * 检测用户授权
     * @param string $query
     * @param bool $is_openid
     * @return bool
     */
    public static function is_terminate(string $query, bool $is_openid = true)
    {
        if ($is_openid){
            $data = self::queryOpenid($query);

            if ($data['authorization_state'] == 'AVAILABLE'){

                return true;
            }

            return false;
        }else{

            $data = self::queryAuthorizationCode($query);

            if ($data['authorization_state'] == 'AVAILABLE'){

                return true;
            }

            return false;
        }
    }



    /**
     * 创建支付分订单
     * @param array $data
     * @return mixed
     */
    public static function createOrder(array $data)
    {
        return self::sendPost('payscore/serviceorder',collect([
            'appid'     => self::$app_id,
            'service_id'=> self::$service_id,
        ])->merge($data)->toArray());
    }

    /**
     * 查询支付分订单
     * @param string $order_id
     * @return mixed
     */
    public static function queryOrder(string $order_id)
    {
        return self::sendGet('payscore/serviceorder',collect([
            'appid'     => self::$app_id,
            'service_id'=> self::$service_id,
        ])->merge([
            'out_order_no'  => $order_id
        ])->toArray());
    }

    /**
     * 取消支付分订单
     * @param string $out_order_no
     * @param string $reason
     * @return mixed
     */
    public static function cancelOrder(string $out_order_no,string $reason)
    {
        return self::sendPost('payscore/serviceorder/'.$out_order_no.'/cancel',collect([
            'appid'     => self::$app_id,
            'service_id'=> self::$service_id,
        ])->merge([
            'reason'    => $reason
        ])->toArray());
    }

    /**
     * 修改订单金额
     * @param string $out_order_no
     * @param array $data
     * @return mixed
     */
    public static function modifyOrder(string $out_order_no,array $data)
    {
        return self::sendPost('payscore/serviceorder/'.$out_order_no.'/modify',collect([
            'appid'     => self::$app_id,
            'service_id'=> self::$service_id,
        ])->merge($data)->toArray());
    }

    /**
     * 完成订单
     * @param string $out_order_no
     * @param array $data
     * @return mixed
     */
    public static function completeOrder(string $out_order_no,array $data)
    {
        return self::sendPost('payscore/serviceorder/'.$out_order_no.'/complete',collect([
            'appid'     => self::$app_id,
            'service_id'=> self::$service_id,
        ])->merge($data)->toArray());
    }

    /**
     * 用户催收
     * @param string $out_order_no
     * @param array $data
     * @return mixed
     */
    public static function payOrder(string $out_order_no)
    {
        return self::sendPost('payscore/serviceorder/'.$out_order_no.'/pay',[
            'appid'     => self::$app_id,
            'service_id'=> self::$service_id,
        ]);
    }

    /**
     * 同步订单
     * @param string $out_order_no
     * @param array $data
     * @return mixed
     */
    public static function syncOrder(string $out_order_no,array $data)
    {
        return self::sendPost('payscore/serviceorder/'.$out_order_no.'/sync',collect([
            'appid'     => self::$app_id,
            'service_id'=> self::$service_id,
        ])->merge($data)->toArray());
    }

    /**
     * 订单退款
     * @param string transaction_id
     * @param array $data
     * @return \Psr\Http\Message\StreamInterface
     */
    public static function orderRefund(string $transaction_id, array $data)
    {
        return self::sendPost('refund/domestic/refunds',collect([
            'transaction_id'  => $transaction_id,
        ])->merge($data)->toArray());
    }

    /**
     * 查询退款
     * @param string $out_refund_no
     * @return mixed
     */
    public static function queryOrderRefund(string $out_refund_no)
    {
        return self::sendGet('refund/domestic/refunds/'.$out_refund_no,[]);
    }

    /**
     * Decrypt AEAD_AES_256_GCM ciphertext
     *
     * @param string    $associatedData     AES GCM additional authentication data
     * @param string    $nonceStr           AES GCM nonce
     * @param string    $ciphertext         AES GCM cipher text
     *
     * @return string|bool      Decrypted string on success or FALSE on failure
     */
    public static function decryptToString($associatedData, $nonceStr, $ciphertext)
    {
        $ciphertext = \base64_decode($ciphertext);
        if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
            return false;
        }

        // ext-sodium (default installed on >= PHP 7.2)
        if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') &&
            \sodium_crypto_aead_aes256gcm_is_available()) {
            $data = \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, self::entity()->api_key);
        }

        // ext-libsodium (need install libsodium-php 1.x via pecl)
        if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') &&
            \Sodium\crypto_aead_aes256gcm_is_available()) {
            $data = \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, self::entity()->api_key);
        }

        // openssl (PHP >= 7.1 support AEAD)
        if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
            $ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
            $authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE);

            $data = openssl_decrypt($ctext, 'aes-256-gcm', self::entity()->api_key, \OPENSSL_RAW_DATA, $nonceStr,
                $authTag, $associatedData);
        }

        if (!isset($data)){
            throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
        }

        return json_decode($data,true);
    }

    /**
     * 付款到银行卡
     * @param string $out_trade_no
     * @param string $bank_name
     * @param string $bank_card
     * @param string $name
     * @param float $price
     * @param string $desc
     * @return false|mixed
     */
    public static function payBank(string $out_trade_no,string $bank_name, string $bank_card,string $name, float $price,$desc = '')
    {
        $self = new WechatPayment();
        $wechat_pay = new WechatEnterprisePay(
            self::$app_id,
            $self->mch_id,
            $self->api_key,
            $self->cert,
            $self->secret,
        );

        return $wechat_pay->payBank(
            $out_trade_no,
            intval($price*100),
            $bank_name,
            $bank_card,
            $name,
            $desc,
        );
    }

    /**
     * 查询付款记录
     * @param string $trade_no
     * @return false|mixed
     */
    public static function queryPayBankNo(string $trade_no)
    {
        $self = new WechatPayment();
        $wechat_pay = new WechatEnterprisePay(
            self::$app_id,
            $self->mch_id,
            $self->api_key,
            $self->cert,
            $self->secret,
        );

        return $wechat_pay->queryOrder($trade_no);
    }

    /**
     * 付款到零钱
     * @param string $out_trade_no
     * @param string $openid
     * @param float $price
     * @param string $name
     * @param string $desc
     * @return WechatEnterprisePay|false|mixed
     */
    public static function transfer(string $out_trade_no,string $openid,float $price,string $name, string $desc)
    {
        $self = new WechatPayment();
        $wechat_pay = new WechatEnterprisePay(
            self::$app_id,
            $self->mch_id,
            $self->api_key,
            $self->cert,
            $self->secret,
        );

        return $wechat_pay->payChange($out_trade_no,$openid,intval($price*100),$name,$desc);
    }

}
